package com.smrtcold.dev.dto.dev.base;

import com.github.xingshuangs.iot.protocol.s7.enums.EPlcType;
import com.github.xingshuangs.iot.protocol.s7.service.S7PLC;
import com.smrtcold.dev.init.SystemInit;
import com.smrtcold.dev.utils.Utils;
import lombok.extern.log4j.Log4j2;

import java.net.SocketTimeoutException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Log4j2
public class XMZDevInfo {


    //    public Cache< String, Point> cache = Caffeine.newBuilder().expireAfterWrite(1, TimeUnit.HOURS).build();
    private static Pattern pattern = Pattern.compile("([A-Za-z]+)(\\d+(\\.\\d+)?)");
    //链接对象
    private S7PLC plc;

    //链接状态
    private boolean status = false;
    private Integer scantime;
    private String apid;
    private String devid;

    //配置数据
    private List<FboxConfData> CONF = new ArrayList<>();
    private Map<String, String> NPMAP = new HashMap<>();
    private Map<String, List<Point>> GROUPPOINT = new HashMap<>();

    public XMZDevInfo(EPlcType plcType, String ip, int port, int scantime, String apid, String devid, List<FboxConfData> list) {
        this.CONF = list;
        this.scantime = scantime;
        this.apid = apid;
        this.devid = devid;
        this.plc = new S7PLC(plcType, ip, port);
        this.plc.setReceiveTimeout(30000);
        this.plc.setConnectTimeout(60000);
        try {
            this.status = this.plc.checkConnected();
            if (null == list || list.isEmpty()) return;
            String addres = null;
            for (FboxConfData fboxConfData : list) {
                if (null == fboxConfData.items) continue;
                List<FboxItem> items = fboxConfData.items;
                for (FboxItem item : items) {
                    addres = item.getAddrDesc();
                    String _suf = addres.split(" ")[0];
                    int i = _suf.lastIndexOf(".");
                    if (i > 0) {
                        _suf = _suf.substring(i + 1);
                        addres = addres.replaceAll("." + _suf, "");
                    }
                    NPMAP.put(item.getName(), addres);
                    if (!GROUPPOINT.containsKey(_suf)) {
                        GROUPPOINT.put(_suf, new ArrayList<>());
                    }
                    GROUPPOINT.get(_suf).add(new Point(item.getName(), addres));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public S7PLC plc() {
        return this.plc;
    }

    public String getDevid() {
        return devid;
    }

    public boolean getStatus() {
        return status;
    }

    public Integer getScantime() {
        return scantime;
    }

    public boolean checkConnected() {
        return this.plc.checkConnected();
    }

    public void dist() {
        if (null != this.plc) {
            this.plc = null;
        }
    }

    // 正则表达式匹配字母开头后跟任意数量的字母或小数点（但只匹配一个小数点），然后是任意数量的数字
    public String splitAlphaNumeric(String input) {
        Matcher matcher = pattern.matcher(input);
        return matcher.find() ? matcher.group(1) : input.split(" ")[0];
    }

    public Number readName(String name) throws Exception {
        String addres = NPMAP.get(name);
        if (null != addres) {
            return readAddres(addres);
        }
        return null;
    }

    public boolean writeByName(String name, Number value) throws Exception {
        String addres = NPMAP.get(name);
        if (null != addres) {
            return writeByAddres(addres, value);
        }
        return false;
    }

    public List get(String... addres) throws Exception {
        String _suf = addres[0].split(" ")[0];
        int i = _suf.lastIndexOf(".");
        if (i > 0) {
            _suf = _suf.substring(i + 1);
            for (int j = 0; j < addres.length; j++) {
                addres[j] = addres[j].replaceAll("." + _suf, "");
            }
        }
        if (_suf.length() > 1) {
            return this.batch(_suf, addres);
        } else {
            Pattern pattern = Pattern.compile("^[A-Z]+");
            Matcher matcher = pattern.matcher(_suf.trim());
            if (matcher.find()) {
                return this.batch( matcher.group(), addres);
            }
            return null;
        }
    }

    public List batch(String _suf, String... addres) throws Exception {
        this.checkStatus();
        for (int i = 0; i < 2; i++) {
            try {
                return readBatch(_suf, addres);
            } catch (Exception e) {
                this.plc.coldRestart();
                Thread.sleep(500);
            }
        }
        return null;
    }

    private List<? extends Number> readBatch(String _suf, String[] addres) {
        switch (_suf) {
            case "VD":
                return this.plc.readFloat32(addres);
            case "VW":
                return this.plc.readInt16(addres);
            case "VB":
            case "M":
            case "B":
            case "Q":
            case "V":
                List<Boolean> booleans = this.plc.readBoolean(addres);
                return booleans.stream().map(b -> b ? 1 : 0).collect(Collectors.toList());
            case "SRW":
                return null;
            default:
                // log.warn("未找到：{}", point);
                return null;
        }
    }


    public Number readAddres(String addres) throws Exception {
        this.checkStatus();
        if (!status) return null;
        String _suf = addres.split(" ")[0];
        int i = _suf.lastIndexOf(".");
        if (i > 0) {
            _suf = _suf.substring(i + 1);
            addres = addres.replaceAll("." + _suf, "");
        }

        switch (_suf) {
            case "VD":
                return this.plc.readFloat32(addres);//ReadUInt32  ReadInt16  ReadDouble}
            case "VW":
                return this.plc.readInt16(addres);
            case "V.B":
            case "M.B":
            case "I.B":
                return this.plc.readBoolean(addres.replaceAll(".B", "B")) ? 1 : 0;
            case "VB":
            case "M":
            case "B":
            case "Q":
            case "V":

                return this.plc.readBoolean(addres) ? 1 : 0;

            case "SRW":
                return null;
            default:
                // log.warn("未找到：{}", point);
                return null;
        }

    }

    public boolean writeByAddres(String addres, Number value) throws Exception {
        this.checkStatus();
        if (!status) return false;
        for (int i = 0; i < 3; i++) {
            try {
                boolean write = this.write(addres, value);
                return write;
            } catch (Exception e) {
                this.plc.coldRestart();
                Thread.sleep(1000);
            }
        }
        return false;

    }

    private boolean write(String addres, Number value) {
        String _suf = splitAlphaNumeric(addres);
        switch (_suf) {
            case "VD":
                //  log.info("地址：{}，值：{}", addres, value.floatValue());
                this.plc.writeFloat32(addres, value.floatValue());//ok
                return true;
            case "VW":
                //log.info("地址：{}，值：{}", addres, value.shortValue());
                this.plc.writeInt16(addres, value.shortValue());

                return true;
            case "VB":
                //log.info("地址：{}，值：{}：{}", addres, value.floatValue());
                this.plc.writeFloat32(addres, value.floatValue());//ok不需要倍率
                return true;
            case "V":
            case "M":
            case "I":
            case "B":
            case "V.B":
            case "M.B":
            case "I.B":
                this.plc.writeBoolean(addres.replaceAll(".B", "B"), 1 == value.intValue());//ok不需要倍率
                return true;
            default:
                log.warn("未找到：{}", _suf);
                return false;
        }
    }


/**
 * 扫描全部数据
 * isALL 是否全量扫描  false:只上报有变化的点 true：上传全部的
 * isMap 是否返回map  false:返回list true：返回map
 *
 * @return
 */

    /**
     * 扫描全部数据
     * isALL 是否全量扫描  false:只上报有变化的点 true：上传全部的
     * isMap 是否返回map  false:返回list true：返回map
     *
     * @return
     */
    public Object readll(boolean isALL, boolean isMap) throws Exception {
        this.checkStatus();
        if (!status) return null;
        Number value = null;
        Point point = null;
        Map<String, Number> resMap = new HashMap<>();
        List<Point> resList = new ArrayList<>();
        long l = System.currentTimeMillis();
        for (Map.Entry<String, List<Point>> stringMapEntry : this.GROUPPOINT.entrySet()) {
            String key = stringMapEntry.getKey();
            List<Point> points = stringMapEntry.getValue();
            // 拆分批次，每个批次200个
            List<List<String>> batches = new ArrayList<>();
            for (int i = 0; i < points.size(); i += 200) {
                List<Point> points1 = points.subList(i, Math.min(i + 200, points.size()));
                String[] addres = points1.stream()
                        .map(Point::getAddres)
                        .toArray(String[]::new);
                List<Number> batch = this.batch(key, addres);
                if (null != batch) {
                    for (int j = 0; j < points1.size(); j++) {
                        value = batch.get(j);
                        if (null != value) {
                            point = points1.get(j);
                            if (isALL || point.isChange(value, l)) {
                                point.setValue(value);
                                if (isMap) {
                                    resMap.put(point.getName(), value);
                                } else {
                                    resList.add(point);
                                }
                            }
                        } else {
                            log.warn("{}--{}", this.devid, key, addres[i], value, l, System.currentTimeMillis(), points.get(i).getName(), points.get(i).getAddres(), batch);
                        }
                    }
                }
            }
        }
        return isMap ? resMap : resList;
    }

    public Map<String, Number> getValues(List<Point> redpoints) throws Exception {
        this.checkStatus();
        if (!status) return null;
        Number value = null;
        String _addres = null;
        Map<String, Number> resMap = new HashMap<>();
        long l = System.currentTimeMillis();
        Map<String, List<Point>> groupPoint = new HashMap<>();
        for (Point item : redpoints) {
            _addres = item.getAddres();
            String _suf = _addres.split(" ")[0];
            int i = _suf.lastIndexOf(".");
            if (i > 0) {
                _suf = _suf.substring(i + 1);
                _addres = _addres.replaceAll("." + _suf, "");
            }
            if (!groupPoint.containsKey(_suf)) {
                groupPoint.put(_suf, new ArrayList<>());
            }
            groupPoint.get(_suf).add(new Point(item.getName(), _addres));
        }


        for (Map.Entry<String, List<Point>> stringMapEntry : groupPoint.entrySet()) {
            String key = stringMapEntry.getKey();
            List<Point> points = stringMapEntry.getValue();
            String[] addres = points.stream()
                    .map(Point::getAddres)
                    .toArray(String[]::new);
            List<Number> batch = this.batch(key, addres);
            if (null != batch) {
                for (int i = 0; i < points.size(); i++) {
                    value = batch.get(i);
                    if (null != value) {
                        resMap.put(points.get(i).getAddres(), value);
                    } else {
                        log.warn("{}--{}", this.devid, key, addres[i], value, l, System.currentTimeMillis(), points.get(i).getName(), points.get(i).getAddres(), batch);
                    }
                }
            }
        }
        return resMap;
    }


    public void checkStatus() throws Exception {
        try {
            boolean status = this.plc.checkConnected();
            //尝试重连
            if (!status) {
                Thread.sleep(200);
                this.plc.hotRestart();
                Thread.sleep(200);
                status = this.plc.checkConnected();
            }
            if (this.status != status) {
                this.status = status;
                //todo  mqtt转发设备链接状态
                log.warn("设备：{} 链接状态：{}", devid, status);
                SystemInit.addMsg(String.format("HMI/%s/%s/status", apid, devid), this.status ? "1" : "0");

            }
        } catch (com.github.xingshuangs.iot.exceptions.SocketTimeoutException e) {
            log.error("设备：{} 链接超时 SocketTimeoutException ", devid);
            throw new SocketTimeoutException(e.getMessage());
        } catch (Exception e) {
            try {
                this.plc.hotRestart();
                Thread.sleep(200);
                log.error("设备：{} coldRestart", devid);
            } catch (Exception ex) {
                log.error("设备：{} coldRestart ERR:{}", devid, e.getMessage());
                throw new SocketTimeoutException(e.getMessage());
            }

        }
    }

    public Number readPoint(Point p) throws Exception {
        Number number = this.readAddres(p.getAddres());
        if (null == p.getHex() || 1 == p.getHex() && 0 == p.getHex()) {
            p.setValue(number);
        } else {
            p.setValue(number.floatValue() * p.getHex());
        }
        if (null != p.getRadix() && 0.0 != p.getRadix()) {
            p.setValue(p.getValue().floatValue() / p.getRadix());
        }
        return p.getValue();
    }


}
